#ifndef SCENEELEMENT_HEADER_VIZMF_PROJECT
#define SCENEELEMENT_HEADER_VIZMF_PROJECT

#include "src/core/generic/vector3.h"
#include "src/gui/itemwidget.h"
#include "src/core/scene/material.h"

class SceneElement {
public:
    enum prim_dim {unknown=0, d2, d3};
//    virtual void drawMe() = 0;
    //virtual ~SceneElement() = 0;
    virtual void drawMe(ItemWidget* drawer) const = 0;

    const Vector3& coord() const { return c_; }
    float radius() const { return  r_; }
    void setMaterial(const Material& c) { col_ = c; }
    const Material& material() const { return col_; }

protected:
    Material col_;
    Vector3 c_;
    float r_;
};

// Scene3DElement helper class
class Scene3DElement : public SceneElement {
public:
  ~Scene3DElement() {}
  enum prim_type {unknown=0, sphere, cylinder, parallelepiped, surface, point, line, cone };
  prim_dim dim() const { return d3; }
};

// 3D elements start the list below...
class Sphere : public Scene3DElement {
public:
    typedef float float_type;

    prim_type type() const {return sphere;}

    Sphere(const Vector3& center, float_type radius, const Material& c)
    {
      col_ = c;
      r_ = radius;
      c_ = center;
    }
    ~Sphere(){}    // a stub for vitual destructor
    void drawMe(ItemWidget *drawer) const
    {
      drawer->drawSphere(c_, r_, col_);
    }

//    Vector3 center() const {return center_;}; // provided by SceneElement
//    float_type radius() const {return radius_;}
};

class Cylinder : public Scene3DElement {
public:
    typedef float float_type;

    prim_type type() const {return cylinder;}

    Cylinder(const Vector3& f, const Vector3& t, const float_type& rada, const float_type& radb, const Material& col ) : center1_(f), center2_(t), rad1_(rada), rad2_(radb)
    {
      col_ = col;
      c_ = (f + t) / 2;
      float r = (rada > radb) ? rada : radb;
      r_ = sqrt( pow ( ( (f - t) / 2 ).length(), 2.0 ) + r * r ) ;
    }
    ~Cylinder(){}

    Vector3 from() const {return center1_;}
    float_type radiusFrom() const {return rad2_;}
    Vector3 to() const {return center2_;}
    float_type radiusTo() const {return rad2_;}

    void drawMe(ItemWidget *drawer) const
    {
      drawer->drawCylinder(center1_, center2_, rad1_, rad2_, col_);
    }

protected:
    Vector3 center1_, center2_;
    float_type rad1_, rad2_;
};

class Cone : public Scene3DElement {
public:
  typedef float float_type;

  prim_type type() const {return cone;}

  Cone(const Vector3& from, const Vector3& to, const float_type rad, const Material& col) : from_(from), to_(to), rad_(rad)
  {
    col_ = col;
    c_ = (from + to) / 2;
    r_ = sqrt( pow ( ( (from - to) / 2 ).length(), 2.0 ) + rad * rad ) ;
  }
  ~Cone() {}

  const Vector3& from() const { return from_; }
  const Vector3& to() const { return to_; }
  float_type radius() const { return rad_; }
  void drawMe(ItemWidget *drawer) const
  {
    drawer->drawCone(from_, to_, rad_, col_);
  }

private:
  Vector3 from_, to_;
  float_type rad_;
};

class Parallelepiped : public Scene3DElement {
public:
  typedef float float_type;

  prim_type type() const {return parallelepiped;}

  Parallelepiped(Vector3 vertex, Vector3 a, Vector3 b, Vector3 c, const Material& col) // let there be crystallographic coordinates! :)
    : vertex_(vertex), a_(a), b_(b), c_(c)
  {
    col_ = col;
    c_ =  vertex + ( (a+b+c) / 3 );
    float x = (a - b).length(), y = (a - c).length(), z = (c - b).length();
    r_ = ( ( r_ = ( x > y ) ? x : y ) > z ) ? r_ : z;
  }
  ~Parallelepiped(){}

  Vector3 vertex () const { return vertex_;}
  Vector3 a() const {return a_;}
  Vector3 b() const {return b_;}
  Vector3 c() const {return c_;}
  void drawMe(ItemWidget *drawer) const
  {
    drawer->drawParallelepiped(vertex_, a_, b_, c_, col_);
  }

protected:
  Vector3 vertex_;
  Vector3 a_, b_, c_;
};

// for now it's just a stub
class Surface : public Scene3DElement {
public:
  typedef float float_type;

  prim_type type() const {return surface;}

  Surface() {}
  ~Surface(){}
  void drawMe(ItemWidget *drawer) const {}

protected:
//  Mesh mesh_;
// etc.
};

// Scene2DElement helper class
class Scene2DElement : public SceneElement {
public:
  enum prim_type {unknown=0, point, line, circle, ellipse, rectangle };
  prim_dim dim() const { return d2; }
};

// from now on Vector3 is used to represent {X, Y} coordinate pair

class Point : public Scene3DElement {
public:
  typedef float float_type;
  typedef Vector3 vector_type;

  prim_type type() const {return point;}

  Point(const vector_type& vec, const Material& Material) : v_(vec) {
    v_[2] = vector_type::float_type(0.0); // a hack for Vector3, harmful in the other cases!
    col_ = Material;
  }
  vector_type vector() const { return v_; }
  void drawMe(ItemWidget *drawer) const {}


protected:
  vector_type v_;
};

class Line : public Scene3DElement {
public:
  typedef float float_type;
  typedef Vector3 vector_type;

  prim_type type() const {return line;}

  Line(const vector_type& vec1, const vector_type& vec2, const Material& Material) : vec1_(vec1), vec2_(vec2)
  {
    c_ = (vec1 + vec2) /2;
    r_ = (vec1 - vec2).length() / 2;
    col_ = Material;
  }
  ~Line(){}

  vector_type vec1() const {return vec1_;}
  vector_type vec2() const {return vec2_;}
  void drawMe(ItemWidget *drawer) const {}

protected:
  vector_type vec1_, vec2_;
};

class Circle : public Scene2DElement {
public:
  typedef float float_type;
  typedef Vector3 vector_type;

  prim_type type() const {return circle;}

  Circle(const vector_type& center, const float_type& radius, const Material& mat) : center_(center), radius_(radius)
  {
    c_ = center;
    r_ = radius;
    col_ = mat;
  }
  ~Circle(){}

  vector_type center() const {return center_;}
  float_type radius() const {return radius_;}
  void drawMe(ItemWidget *drawer) const {}

protected:
  vector_type center_;
  float_type radius_;
};

//class Ellipse : public Scene2DElement {/* todo: to fill :) */};

class Rectangle : public Scene2DElement {
public:
  typedef float float_type;
  typedef Vector3 vector_type;

  prim_type type() const {return rectangle;}

  Rectangle(const vector_type& vertex, const vector_type& dx, const vector_type& dy, const Material& Material) : vertex_(vertex), dx_(dx), dy_(dy)
  {
    c_ = vertex + ( (dx + dy) / 2 );
    r_ = (dx+dy).length() / 2;
    col_ = Material;
  }
  ~Rectangle(){}

  const vector_type& vertex() const {return vertex_;}
  const vector_type& dx() const {return dx_;}
  const vector_type& dy() const {return dy_;}
  void drawMe(ItemWidget *drawer) const {}

protected:
  vector_type vertex_, dx_, dy_;
};

/*
todo:
  1) define high level properties of objects stored in SceneElements
  2) decide, who is drawing who :)
  3) decide if the properties such as Material, shininess, material, etc. should be stored inside elements or it would be wiser to keep them in
    a separate array associated somehow with elements
  4) decide if we need support for different float types here and in which form if so (template, hardcode in Base classes, etc.)
  5) do we need separate Vector2 or can we do the same with Vector3
  6) which definition is the best for ellipse
*/

#endif // SCENEELEMENT_HEADER_VIZMF_PROJECT
